Dify 召回测试方案剖析

概览

召回测试是基于给定的查询文本测试知识库的召回效果。召回测试在知识库中非常重要,因为它直接影响到系统的全面性、用户需求的满足、系统性能的改进以及特定应用场景的需求。

Dify 中的召回测试

接口信息

接口 /console/api/datasets/dataset的id/hit-testing, 发起请求时,会传入检索的方式(全文检索,向量检索,混合检索),如果有 reranking_model 模型,还会传入相关信息。然后就是 score_threshold,top_k 等参数。

召回过程

  1. 获取 dataset,并且校验 dataset 的权限,接口参数

  2. 调用 HitTestingService.retrieve 函数,主要逻辑都是在其中完成的

  3. 在函数中首选获取 retrieval_model,如果没有则使用默认的

  4. 根据 dataset 的 embedding_model_provider 和 embedding_model 获取对应的 embedding_model

  5. embeddings = CacheEmbedding(embedding_model) 得到一个可缓存的 embedding 对象

  6. 调用 RetrievalService.retrieve 方法进行检索,retrieve 中,首先进行一些简单的校验,校验 dataset 的 document 数量,segment 数量等,如果为0,则就不必继续了

  7. retrival_method 有几种值:keyword_search(关键字搜索),semantic_search(语义搜索),full_text_search(全文搜索),hybrid_search(混合搜索)

  8. keyword_search: 启动一个线程,在线程中执行 RetrievalService.keyword_search 方法。keyword_search 中,根据 dataset 获取一个 Keyword 实例,然后执行 keyword.search 搜索,得到对应的 documents

  9. semantic_search: 启动一个线程,在线程中执行 RetrievalService.embedding_search 方法。embedding_search 中,根据 dataset 获取一个 Vector 实例,然后执行 vector.search_by_vector 方法。首先对 传入的 query 进行 embedding,这其中使用 redis 进行了缓存,如果 hash 的 key 在 redis 中存在则直接取出,否则调用对应的模型进行 embedding,然后在缓存在 redis 中。调用具体的向量数库的 search_by_vector方法,执行查询,查询得到满足条件的 text,将 text 封装成一个 Document 对象,最后返回一个 Document 列表。

    如果选择了 reranking_model,那么还会实例话一个数据后处理的 DataPostProcessor。DataPostProcessor 中有两种对数据处理的方法,一种是 rerank,另一种是 reorder。rerank 是需要模型的,reorder 不需要。会尝试执行这两种方法,先执行rerank,然后再执行 reorder

    调用对应 rerank 模型的 invoke_rerank 方法,底层会去调用每个 rerank 模型的接口。最后将得到的 rerank_documents 返回。

    如果使用了reorder,那么将 rerank_documents 传入 reorder_runner 中继续执行。reorder_runner 中 run 方法,将奇数下标的元素保持原有顺序,而偶数下标的元素则被反转。例如 documents = [doc1, doc2, doc3, doc4, doc5, doc6, doc7, doc8],最后结果为 new_documents = [doc1, doc8, doc3, doc6, doc5, doc4, doc7, doc2]。

  10. full_text_index_search: full_text_index_search 中,根据 dataset 获取一个 Vector 实例,然后执行 vector.search_by_full_text 方法得到对应的 documents。如果有 reranking_model 模型,则同样执行 DataPostProcessor invoke 方法,进行数据后处理

  11. hybrid_search: 如果 retrival_method 为 hybrid_search,则还会进行一次 DataPostProcessor.invoke,取 top_k 条数据。

  12. 循环 documents,拿去到每一个 document 的 doc_id,然后去查询 DocumentSegment,最后得到的 DocumentSegment 就是从数据库中查询出来的相似的文本片段。最后将结果返回前端。

总结

检索方法 检索详情
keyword_search(关键字搜索) 1. 启动一个线程,在线程中执行 RetrievalService.keyword_search 方法。
2. keyword_search 中,根据 dataset 获取一个 Keyword 实例,然后执行 keyword.search 搜索,得到对应的 documents
semantic_search(语义搜索) 1. 对传入的 query 进行 embedding
2. 使用 redis 进行了缓存,如果 hash 的 key 在 redis 中存在则直接取出,否则调用对应的模型进行 embedding,最后将结果写会 redis 中。
3. 调用具体的向量数库的 search_by_vector方法,执行查询,查询得到满足条件的 text,将 text 封装成一个 Document 对象,最后返回一个 Document 列表。
4. 如果选择了 reranking_model,那么还有一个数据后处理,先后执行 rerank 和 reorder。
full_text_search(全文搜索) 1. 根据 dataset 获取一个 Vector 实例,然后执行 vector.search_by_full_text 方法得到对应的 documents。
2. 如果有 reranking_model 模型,则同样执行 DataPostProcessor invoke 方法,进行数据后处理
hybrid_search(混合搜索) 1. 最后还会再次进行一次数据后处理,取 top_k 条数据

Q:search_by_full_text 方法和 search_by_vector 有什么区别?

A:search_by_full_text 使用的是 bm25,search_by_vector 方法是传入要查询的向量,然后查询出相似的结果。

Q:redis 中缓存的key是什么,过期时间是多少,缓存的数据格式是什么?

A:key 是 provider + model + hash 值。过期时间是 600s。数据格式是将向量转换为 bytes 后的 base64 的值。